# uncompyle6 version 3.2.3
# Python bytecode 3.6 (3379)
# Decompiled from: Python 3.6.8 |Anaconda custom (64-bit)| (default, Feb 21 2019, 18:30:04) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: site-packages\PyInstaller\depend\utils.py
"""
Utility functions related to analyzing/bundling dependencies.
"""
import ctypes, ctypes.util, dis, io, marshal, os, re, zipfile
from ..lib.modulegraph import util, modulegraph
from .. import compat
from ..compat import (
    is_darwin,
    is_unix,
    is_py2,
    is_py34,
    is_freebsd,
    BYTECODE_MAGIC,
    PY3_BASE_MODULES,
    exec_python_rc,
)
from .dylib import include_library
from .. import log as logging

logger = logging.getLogger(__name__)


def create_py3_base_library(libzip_filename, graph):
    """
    Package basic Python modules into .zip file. The .zip file with basic
    modules is necessary to have on PYTHONPATH for initializing libpython3
    in order to run the frozen executable with Python 3.
    """

    def _write_long(f, x):
        """
        Write a 32-bit int to a file in little-endian order.
        """
        f.write(bytes([x & 255, x >> 8 & 255, x >> 16 & 255, x >> 24 & 255]))

    regex_modules = ("|").join(["(^%s$)" % x for x in PY3_BASE_MODULES])
    regex_submod = ("|").join(["(^%s\\..*$)" % x for x in PY3_BASE_MODULES])
    regex_str = regex_modules + "|" + regex_submod
    module_filter = re.compile(regex_str)
    try:
        if os.path.exists(libzip_filename):
            os.remove(libzip_filename)
        logger.debug("Adding python files to base_library.zip")
        with zipfile.ZipFile(libzip_filename, mode="w") as (zf):
            zf.debug = 3
            for mod in graph.flatten():
                if type(mod) in (modulegraph.SourceModule, modulegraph.Package):
                    if module_filter.match(mod.identifier):
                        st = os.stat(mod.filename)
                        timestamp = int(st.st_mtime)
                        size = st.st_size & 4294967295
                        if type(mod) is modulegraph.Package:
                            new_name = (
                                mod.identifier.replace(".", os.sep)
                                + os.sep
                                + "__init__"
                                + ".pyc"
                            )
                        else:
                            new_name = mod.identifier.replace(".", os.sep) + ".pyc"
                        with io.BytesIO() as (fc):
                            fc.write(BYTECODE_MAGIC)
                            _write_long(fc, timestamp)
                            _write_long(fc, size)
                            marshal.dump(mod.code, fc)
                            zf.writestr(new_name, fc.getvalue())

    except Exception as e:
        logger.error("base_library.zip could not be created!")
        raise


def scan_code_for_ctypes(co):
    binaries = []
    __recursivly_scan_code_objects_for_ctypes(co, binaries)
    binaries = set(binaries)
    for binary in list(binaries):
        if not binary:
            binaries.remove(binary)
        elif binary != os.path.basename(binary):
            try:
                filename = co.co_filename
            except:
                filename = "UNKNOWN"

            logger.warning(
                "Ignoring %s imported from %s - ctypes imports are only supported using bare filenames",
                binary,
                filename,
            )
            binaries.remove(binary)

    binaries = _resolveCtypesImports(binaries)
    return binaries


def __recursivly_scan_code_objects_for_ctypes(co, binaries):
    binaries.extend(__scan_code_instruction_for_ctypes(util.iterate_instructions(co)))


def __scan_code_instruction_for_ctypes(instructions):
    """
    Detects ctypes dependencies, using reasonable heuristics that
    should cover most common ctypes usages; returns a tuple of two
    lists, one containing names of binaries detected as
    dependencies, the other containing warnings.
    """

    def _libFromConst():
        """Extracts library name from an expected LOAD_CONST instruction and
        appends it to local binaries list.
        """
        instruction = next(instructions)
        if instruction.opname == "LOAD_CONST":
            soname = instruction.argval
            if isinstance(soname, str):
                return soname

    while True:
        try:
            instruction = next(instructions)
            expected_ops = ("LOAD_GLOBAL", "LOAD_NAME")
            if not instruction or instruction.opname not in expected_ops:
                continue
            name = instruction.argval
            if name == "ctypes":
                expected_ops = ("LOAD_ATTR",)
                instruction = next(instructions)
                if instruction.opname not in expected_ops:
                    continue
                name = instruction.argval
            if name in ("CDLL", "WinDLL", "OleDLL", "PyDLL"):
                yield _libFromConst()
            else:
                if name in ("cdll", "windll", "oledll", "pydll"):
                    instruction = next(instructions)
                    if instruction.opname == "LOAD_ATTR":
                        if instruction.argval == "LoadLibrary":
                            yield _libFromConst()
                        else:
                            yield instruction.argval + ".dll"
                    else:
                        if instruction.opname == "LOAD_ATTR" and name in ("util",):
                            instruction = next(instructions)
                            if instruction.opname == "LOAD_ATTR":
                                if instruction.argval == "find_library":
                                    libname = _libFromConst()
                                    if libname:
                                        lib = ctypes.util.find_library(libname)
                                        if lib:
                                            yield os.path.basename(lib)
        except StopIteration:
            break


def _resolveCtypesImports(cbinaries):
    """
    Completes ctypes BINARY entries for modules with their full path.
    
    Input is a list of c-binary-names (as found by
    `scan_code_instruction_for_ctypes`). Output is a list of tuples
    ready to be appended to the ``binaries`` of a modules.
    
    This function temporarily extents PATH, LD_LIBRARY_PATH or
    DYLD_LIBRARY_PATH (depending on the plattform) by CONF['pathex']
    so shared libs will be search there, too.
    
    Example:
    >>> _resolveCtypesImports(['libgs.so'])
    [(libgs.so', ''/usr/lib/libgs.so', 'BINARY')]
    
    """
    global LDCONFIG_CACHE
    from ctypes.util import find_library
    from ..config import CONF

    if is_unix:
        envvar = "LD_LIBRARY_PATH"
    else:
        if is_darwin:
            envvar = "DYLD_LIBRARY_PATH"
        else:
            envvar = "PATH"

        def _setPaths():
            path = os.pathsep.join(CONF["pathex"])
            old = compat.getenv(envvar)
            if old is not None:
                path = os.pathsep.join((path, old))
            compat.setenv(envvar, path)
            return old

        def _restorePaths(old):
            if old is None:
                compat.unsetenv(envvar)
            else:
                compat.setenv(envvar, old)

        ret = []
        old = _setPaths()
        for cbin in cbinaries:
            cpath = find_library(os.path.splitext(cbin)[0])
            if is_unix:
                if cpath is None:
                    cpath = cbin
                for d in compat.getenv(envvar, "").split(os.pathsep):
                    if os.path.isfile(os.path.join(d, cpath)):
                        cpath = os.path.join(d, cpath)
                        break
                else:
                    if LDCONFIG_CACHE is None:
                        load_ldconfig_cache()
                    if cpath in LDCONFIG_CACHE:
                        cpath = LDCONFIG_CACHE[cpath]
                        if not os.path.isfile(cpath):
                            raise AssertionError
                        else:
                            cpath = None

            if cpath is None:
                if not include_library(cbin):
                    continue
                    logger.warning("library %s required via ctypes not found", cbin)
            elif not include_library(cpath):
                continue
                ret.append((cbin, cpath, "BINARY"))

        _restorePaths(old)
        return ret


LDCONFIG_CACHE = None


def load_ldconfig_cache():
    """
    Create a cache of the `ldconfig`-output to call it only once.
    It contains thousands of libraries and running it on every dynlib
    is expensive.
    """
    global LDCONFIG_CACHE
    if LDCONFIG_CACHE is not None:
        return
    from distutils.spawn import find_executable

    ldconfig = find_executable("ldconfig")
    if ldconfig is None:
        ldconfig = find_executable("ldconfig", "/usr/sbin:/sbin:/usr/bin:/usr/sbin")
        if ldconfig is None:
            LDCONFIG_CACHE = {}
            return
        if is_freebsd:
            text = compat.exec_command(ldconfig, "-r")
            text = text.strip().splitlines()[2:]
            pattern = re.compile("^\\s+\\d+:-l(.+?)((\\.\\d+)+) => (\\S+)")
            pattern = re.compile("^\\s+\\d+:-l(\\S+)(\\s.*)? => (\\S+)")
        else:
            text = compat.exec_command(ldconfig, "-p")
            text = text.strip().splitlines()[1:]
            pattern = re.compile("^\\s+(\\S+)(\\s.*)? => (\\S+)")
        LDCONFIG_CACHE = {}
        for line in text:
            m = pattern.match(line)
            path = m.groups()[-1]
            if is_freebsd:
                bname = os.path.basename(path).split(".so", 1)[0]
                name = "lib" + m.group(1)
                if not name.startswith(bname):
                    raise AssertionError
                name = bname + ".so" + name[len(bname) :]
            else:
                name = m.group(1)
            if name not in LDCONFIG_CACHE:
                LDCONFIG_CACHE[name] = path


def get_path_to_egg(path):
    """
    Return the path to the python egg file, if the path points to a
    file inside a (or to an egg directly).
    Return `None` otherwise.
    """
    lastpath = None
    while path:
        if path != lastpath:
            if os.path.splitext(path)[1].lower() == ".egg":
                if os.path.isfile(path) or os.path.isdir(path):
                    return path
                lastpath = path
                path = os.path.dirname(path)


def is_path_to_egg(path):
    """
    Check if path points to a file inside a python egg file (or to an egg
       directly).
    """
    return get_path_to_egg(path) is not None
